﻿using System;
using System.Diagnostics;
using System.IO;
using System.IO.IsolatedStorage;
using System.Net;
using System.Text;

#if WINDOWS_UWP || NETFX_CORE
using Windows.Security.Cryptography;
using Windows.Security.Cryptography.Core;
using Windows.UI.Xaml.Media.Imaging;
#else
using System.Windows;
using System.Security.Cryptography;
using System.Windows.Media.Imaging;
#endif

namespace Outcoder.Data.ImageCache
{
	/* Based on work by Peter Nowak (http://blogs.compactframework.de/Peter.Nowak/2010/08/12/ldquoIntelligentrdquo+Image+Caching+For+WP+7.aspx)  
	 * and Ingo Rama (http://weblogs.thinktecture.com/ingo/). */
	internal static class ImageDownloader
	{
		const double expirationDays = 1.0;

		public static void DownloadImage(string imageUrl, BitmapImage image,
			ImageCacheItem imageCacheItem)
		{
			ArgumentValidator.AssertNotNull(imageCacheItem, "item");
			string filename = CreateUniqueFilename(imageUrl);
			imageCacheItem.LocalFilename = filename;

			var asyncDataTransfer = new AsyncDataTransfer
			{
				ImageCacheItem = imageCacheItem,
				Image = image
			};

			var httpWebRequest = (HttpWebRequest)WebRequest.Create(imageUrl);
			if (imageCacheItem.ImageId != null)
			{
				httpWebRequest.Headers["If-None-Match"] = imageCacheItem.ImageId;
			}
			asyncDataTransfer.Request = httpWebRequest;

			httpWebRequest.BeginGetResponse(RequestCallback, asyncDataTransfer);
		}

		static void RequestCallback(IAsyncResult asyncResult)
		{
			if (!asyncResult.IsCompleted)
			{
				return;
			}

			AsyncDataTransfer transfer = (AsyncDataTransfer)asyncResult.AsyncState;
			try
			{
				HttpWebResponse response =
					(HttpWebResponse)transfer.Request.EndGetResponse(asyncResult);

				IsolatedStorageFile storageFile =
					IsolatedStorageFile.GetUserStoreForApplication();

				if (response.StatusCode == HttpStatusCode.NotModified)
				{
#if WINDOWS_UWP || NETFX_CORE
					UISynchronizationContext.Instance.InvokeWithoutBlocking(
#else
					Deployment.Current.Dispatcher.BeginInvoke(
#endif
						() =>
						{
							string fileName = transfer.ImageCacheItem.LocalFilename;


							var isolatedStorageFileStream = storageFile.OpenFile(fileName, FileMode.Open);
#if WINDOWS_UWP || NETFX_CORE
							transfer.Image.SetSource(isolatedStorageFileStream.AsRandomAccessStream());
#else
							transfer.Image.SetSource(isolatedStorageFileStream);
#endif
						});
					return;
				}

				transfer.ImageCacheItem.ImageId = response.Headers["ETag"];

				transfer.ImageCacheItem.Expiration = response.Headers["Expires"] != null
					? DateTime.Parse(response.Headers["Expires"])
					: DateTime.Now.AddDays(expirationDays);

				Stream responseStream = response.GetResponseStream();
				using (var writer = new BinaryWriter(
					storageFile.CreateFile(transfer.ImageCacheItem.LocalFilename)))
				{
					var b = new byte[4096];
					int read;
					while ((read = responseStream.Read(b, 0, b.Length)) > 0)
					{
						writer.Write(b, 0, read);
					}
					writer.Flush();
				}

#if WINDOWS_UWP || NETFX_CORE
				UISynchronizationContext.Instance.InvokeWithoutBlocking(
#else
				Deployment.Current.Dispatcher.BeginInvoke(
#endif
					() =>
					{
						string fileName = transfer.ImageCacheItem.LocalFilename;

						var isolatedStorageFileStream = storageFile.OpenFile(fileName, FileMode.Open);
#if WINDOWS_UWP || NETFX_CORE
						transfer.Image.SetSource(isolatedStorageFileStream.AsRandomAccessStream());
#else
						transfer.Image.SetSource(isolatedStorageFileStream);
#endif
					});

				ImageCache.imageCache.Add(
					transfer.Request.RequestUri.ToString(), transfer.ImageCacheItem);

				ImageCache.SaveCachedImageInfo();
			}
			catch (Exception ex)
			{
				Debug.WriteLine("Unable to cache image. " + ex);
			}
		}

		static string CreateUniqueFilename(string url)
		{
			string extension = Path.GetExtension(url);

#if WINDOWS_UWP || NETFX_CORE
			var hash = HashString(url);
			return hash + extension;
#else
			byte[] textToHash = Encoding.UTF8.GetBytes(url);
			var sha1Managed = new SHA1Managed();
			byte[] hash = sha1Managed.ComputeHash(textToHash);
			return BitConverter.ToString(hash) + extension;
#endif

		}

#if WINDOWS_UWP || NETFX_CORE
		static string HashString(string str)
		{
			var bufferString = CryptographicBuffer.ConvertStringToBinary(str, BinaryStringEncoding.Utf8);

			var hashAlgorithmProvider = HashAlgorithmProvider.OpenAlgorithm(HashAlgorithmNames.Sha1);
			var bufferHash = hashAlgorithmProvider.HashData(bufferString);
			return CryptographicBuffer.EncodeToBase64String(bufferHash);

		}
#endif

	}

	internal class AsyncDataTransfer
	{
		public HttpWebRequest Request { get; set; }
		public BitmapImage Image { get; set; }
		public ImageCacheItem ImageCacheItem { get; set; }
	}
}
